package com.hqd.ch03.v50.context.annotation;

import com.hqd.ch03.utils.ClassUtils;
import com.hqd.ch03.v50.boot.context.annotation.ImportBeanDefinitionRegistrar;
import com.hqd.ch03.v50.core.type.AnnotationMetadata;
import com.hqd.ch03.v50.core.type.classreading.MetadataReader;
import com.hqd.ch03.v50.io.DescriptiveResource;
import com.hqd.ch03.v50.io.Resource;
import com.hqd.ch03.v50.reader.BeanDefinitionReader;

import java.util.*;

final class ConfigurationClass {

    /**
     * 跳过方法
     */
    final Set<String> skippedBeanMethods = new HashSet<>();
    /**
     * 注解元信息
     */
    private final AnnotationMetadata metadata;
    /**
     * 资源
     */
    private final Resource resource;
    /**
     * 配置类
     */
    private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);

    private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();

    private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
            new LinkedHashMap<>();

    private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
            new LinkedHashMap<>();
    private String beanName;

    ConfigurationClass(MetadataReader metadataReader, String beanName) {
        this.metadata = metadataReader.getAnnotationMetadata();
        this.resource = metadataReader.getResource();
        this.beanName = beanName;
    }

    /**
     * Create a new {@link ConfigurationClass} representing a class that was imported
     * using the {@link Import} annotation or automatically processed as a nested
     * configuration class (if importedBy is not {@code null}).
     *
     * @param metadataReader reader used to parse the underlying {@link Class}
     * @param importedBy     the configuration class importing this one or {@code null}
     * @since 3.1.1
     */
    ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) {
        this.metadata = metadataReader.getAnnotationMetadata();
        this.resource = metadataReader.getResource();
        this.importedBy.add(importedBy);
    }

    /**
     * Create a new {@link ConfigurationClass} with the given name.
     *
     * @param clazz    the underlying {@link Class} to represent
     * @param beanName name of the {@code @Configuration} class bean
     * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
     */
    ConfigurationClass(Class<?> clazz, String beanName) {
        this.metadata = AnnotationMetadata.introspect(clazz);
        this.resource = new DescriptiveResource(clazz.getName());
        this.beanName = beanName;
    }

    /**
     * Create a new {@link ConfigurationClass} representing a class that was imported
     * using the {@link Import} annotation or automatically processed as a nested
     * configuration class (if imported is {@code true}).
     *
     * @param clazz      the underlying {@link Class} to represent
     * @param importedBy the configuration class importing this one (or {@code null})
     * @since 3.1.1
     */
    ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
        this.metadata = AnnotationMetadata.introspect(clazz);
        this.resource = new DescriptiveResource(clazz.getName());
        this.importedBy.add(importedBy);
    }

    /**
     * Create a new {@link ConfigurationClass} with the given name.
     *
     * @param metadata the metadata for the underlying class to represent
     * @param beanName name of the {@code @Configuration} class bean
     * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
     */
    ConfigurationClass(AnnotationMetadata metadata, String beanName) {
        this.metadata = metadata;
        this.resource = new DescriptiveResource(metadata.getClassName());
        this.beanName = beanName;
    }


    AnnotationMetadata getMetadata() {
        return this.metadata;
    }

    Resource getResource() {
        return this.resource;
    }

    String getSimpleName() {
        return ClassUtils.getShortName(getMetadata().getClassName());
    }


    public String getBeanName() {
        return this.beanName;
    }

    void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    /**
     * Return whether this configuration class was registered via @{@link Import} or
     * automatically registered due to being nested within another configuration class.
     *
     * @see #getImportedBy()
     * @since 3.1.1
     */
    public boolean isImported() {
        return !this.importedBy.isEmpty();
    }

    /**
     * Merge the imported-by declarations from the given configuration class into this one.
     *
     * @since 4.0.5
     */
    void mergeImportedBy(ConfigurationClass otherConfigClass) {
        this.importedBy.addAll(otherConfigClass.importedBy);
    }

    /**
     * Return the configuration classes that imported this class,
     * or an empty Set if this configuration was not imported.
     *
     * @see #isImported()
     * @since 4.0.5
     */
    Set<ConfigurationClass> getImportedBy() {
        return this.importedBy;
    }

    void addBeanMethod(BeanMethod method) {
        this.beanMethods.add(method);
    }

    Set<BeanMethod> getBeanMethods() {
        return this.beanMethods;
    }

    void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
        this.importedResources.put(importedResource, readerClass);
    }

    void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
        this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
    }

    Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> getImportBeanDefinitionRegistrars() {
        return this.importBeanDefinitionRegistrars;
    }

    Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
        return this.importedResources;
    }


    @Override
    public boolean equals(Object other) {
        return (this == other || (other instanceof ConfigurationClass &&
                getMetadata().getClassName().equals(((ConfigurationClass) other).getMetadata().getClassName())));
    }

    @Override
    public int hashCode() {
        return getMetadata().getClassName().hashCode();
    }

    @Override
    public String toString() {
        return "ConfigurationClass: beanName '" + this.beanName + "', " + this.resource;
    }


}
